<?php
/**
 * 表模型类
 * @copyright reginx.com
 * $Id: tab.class.php 10 2013-01-24 09:42:23Z reginx $
 */
class tab {
    /**
     * 表 DB 配置
     *
     * @var unknown_type
     */
    public $dbconf = array();

    /**
     * 表数据
     *
     * @var unknown_type
     */
    public $data = array();

    /**
     * 字段输入验证
     *
     * @var unknown_type
     */
    public $validate  = array();

    /**
     * 字段默认
     *
     * @var unknown_type
     */
    public $default  = array();
    
    /**
     * 字段过滤规则
     *
     * @var unknown_type
     */
    public $filter   = array();

    /**
     * 数据验证错误信息
     *
     * @var unknown_type
     */
    private $_errmsg = array();

    /**
     * 数据库连接对象
     *
     * @var unknown_type
     */
    private  $_dbobj = null;

    /**
     * sql语句构造数组
     *
     * @var unknown_type
     */
    private $_sql   = array();

    /**
     * 表模型配置信息
     *
     * @var unknown_type
     */
    private $_conf  = array();

    /**
     * 数据返回索引 默认为数字
     *
     * @var unknown_type
     */
    private $_rkey  = null;

    /**
     * 构造函数
     * 支持单表定义配置
     */
    public function __construct($class = null ,$dbconf = array()){
        $this->_dbobj = db_extra::getobj(empty($dbconf) ? $GLOBALS['_APP']['db'] : $dbconf);
        $this->_conf = $GLOBALS['_APP']['db'][$GLOBALS['_APP']['db']['class']][$this->_dbobj->serverid()];
        $class = empty($class) ? get_class($this) : $class;
        $this->_dbobj = db_extra::getobj($this->_conf);
        if($class != __CLASS__ ){
            $this->_conf['table'] = substr($class,0,-4);
            $this->_conf['table_name'] = '`'.$this->_conf['pre'].$this->_conf['table'].'`';
            $this->_initab();
        }
    }

    /**
     * 获取表结构
     *
     */
    private function _initab(){
        $this->_conf['fields'] = $GLOBALS['_CACHE']->get('REGINX@'.$this->_conf['table'].'_struct','');
        if(!$this->_conf['fields']){
            $this->_conf['fields'] = $this->_dbobj->getfields($this->_conf['table_name']);
            $GLOBALS['_CACHE']->set('REGINX@'.$this->_conf['table'].'_struct',$this->_conf['fields'],0,'');
        }
    }

    /**
     * where
     *
     * @param unknown_type $where
     * @return unknown
     */
    public function where($str){
        $str = trim($str);
        $instr = array();
        // todo 处理函数语句
        preg_match_all('/(\w+\s*(not\s*)?in\s*\(.+?\))/i' , $str , $in);
        foreach($in[1] as $k => $v){
            $str = str_replace($v,'#####'.$k.'#####' , $str);
            $tmp = preg_split('/\s*(not\s*)?in\s*/i' , $v);
            $instr[$k] = str_replace(trim($tmp[0]),$this->escape($tmp[0]) , $v);
        }
        $str = str_replace(')',' ) ' , str_replace('(','( ',$str));
        preg_match_all('/(\w+)\s*(\>\=|\<\=|\!\=|\=|\>|\<|like)\s*([^\s]+)/i' , $str , $blocks);
        for($i = 0 , $max = count($blocks[0]) ; $i < $max ; $i++){
            $temp = $this->escape($blocks[1][$i]) . ' ' . $blocks[2][$i].' ';
            if(strpos($blocks[3][$i],'\'') === false && ( strpos($blocks[3][$i],'"') !== false || strpos($blocks[3][$i] , '.') !== false) ){
                $temp .= $this->escape($blocks[3][$i]);
            }else{
                $temp .= $blocks[3][$i];
            }
            $str = str_replace($blocks[0][$i] , $temp , $str);
        }
        if($instr){
            foreach($instr as $k => $v){
                $str = str_replace('#####'.$k.'#####' , $v , $str);
            }
            $instr = null;
        }
        $this->_sql['where'][] = $str;
        return $this;
    }

    /**
     * set 字段值
     *
     * @param unknown_type $k
     * @param unknown_type $v
     */
    public function set($k , $v){
        // 检测字段是否存在
        if(!isset($this->_conf['fields']['list'][$k])){
            core::error(core::L('db-field-not-exits' , $k , $this->_conf['table_name'] ) , 'Tab::set' , 1 , $k);
        }
        $this->data[$k] = $v;
        return $this;
    }

    /**
     * limit 
     *
     * @param unknown_type $str
     * @return unknown
     */
    public function limit($str){
        $str = trim($str);
        if($str != ''){
            $tmp = explode(',' , $str);
            $this->_sql['limit'] = array_map('intval',$tmp);
        }
        return $this;
    }

    /**
     * union limit 
     *
     * @param unknown_type $str
     * @return unknown
     */
    public function ulimit($str){
        $str = trim($str);
        if($str != ''){
            $tmp = explode(',' , $str);
            $this->_sql['ulimit'] = array_map('intval',$tmp);
        }
        return $this;
    }

    /**
     * 获取一条记录
     *
     * @return unknown
     */
    public function get(){
        $this->_sql['limit'] = array(1);
        return $this->_dbobj->get($this->parsesql('select'));
    }

    /**
     * 执行sql
     *
     * @return unknown
     */
    public function exec($sql){
        return $this->_dbobj->query($sql);
    }

    /**
     * union
     *
     * @param tab $obj
     * @param boolean $all
     * @return mixed
     */
    public function union(&$obj , $ctype = false){
        $ctype = $ctype ? ($ctype == '1' ? 'all' : 'distinct') : '';
        $sql   = '( '.$this->parsesql('select') . ') union ' . $ctype.' ( ' . $obj->parsesql('select') . ' ) ' ;
        // order
        if(!empty($this->_sql['uorder'])){
            $sql .= ' order by ' . implode(' , ' , $this->_sql['uorder']);
        }
        // limit
        if(!empty($this->_sql['ulimit'])){
            $sql .= ' limit  ' . implode(' , ' , $this->_sql['ulimit']);
        }
        return $this->_dbobj->getall($sql);
    }

    /**
     * 输出sql
     *
     * @param unknown_type $sql
     */
    public function test($act = 'select'){
        if($this->_errmsg){
            return $this->_errmsg;
        }
        return $this->parsesql($act);
    }

    /**
     * 获取数据集合
     *
     * @return unknown
     */
    public  function getall(){
        return $this->_dbobj->getall($this->parsesql('select') , $this->_rkey);
    }

    /**
     * 设置返回数据数组的索引键
     *
     * @param unknown_type $k
     * @return unknown
     */
    public function rkey($k = null){
        $this->_rkey = empty($k) ? $this->_conf['fields']['pk'] : $k;
        return $this;
    }

    /**
     * union all
     *
     * @param tab $obj
     * @return mixed
     */
    public function unionall(&$obj){
        return $this->union($obj , 1);
    }

    /**
     * union distinct
     *
     * @param unknown_type $obj
     * @return unknown
     */
    public function uniondis(&$obj){
        return $this->union($obj , 2);
    }

    /**
     * 解析sql
     *
     * @return unknown
     */
    public function parsesql($act=false){
        // action
        $this->_sql['act'] = $act ? $act : $this->_sql['act'];
        $sql = $this->_sql['act'] . ' ';

        // select
        if($this->_sql['act'] == 'select'){
            // fields
            if($this->_sql['fields']){
                $sql .= implode(' , ' , $this->_sql['fields']);
            }else{
                $sql .= ' * ';
            }
            $this->_sql['fields'] = null;
            $sql .= ' from ' . $this->_conf['table_name'];
            // join
            if(!empty($this->_sql['join'])){
                $sql .= ' ' . implode(' ' , $this->_sql['join']);
                $this->_sql['join'] = null;
            }
            // where
            if(!empty($this->_sql['where'])){
                $sql .= ' where 1 = 1 ';
                foreach ($this->_sql['where'] as $v){
                    $sql .= ' and ( ' . $v . ' ) ';
                }
                $this->_sql['where'] = null;
            }
            // group
            if(!empty($this->_sql['group'])){
                $sql .= ' group by ' . implode(' , ' , $this->_sql['group']);
                $this->_sql['group'] = null;
            }
            // order
            if(!empty($this->_sql['order'])){
                $sql .= ' order by ' . implode(' , ' , $this->_sql['order']);
                $this->_sql['order'] = null;
            }
            // limit
            if(!empty($this->_sql['limit'])){
                $sql .= ' limit  ' . implode(' , ' , $this->_sql['limit']);
                $this->_sql['limit'] = null;
            }
        }
        // update
        else if($this->_sql['act'] == 'update'){
            $sql .= $this->_conf['table_name'] . ' set ';
            // set 数据
            if(!empty($this->data) && is_array($this->data)){
                foreach ($this->data as $k => &$v){
                    if($k != $this->_conf['fields']['pk']){
                        $v = $this->escape($k) . " = " . "'".$v."'";
                    }else{
                        $this->where("{$k} = '{$v}'");
                        unset($this->data[$k]);
                    }
                }
                $sql .= implode(' , ' , $this->data);
            }
            // 无数据 抛出异常信息
            else{
                core::error(core::L('no-data' , $this->_conf['table']) , 'tab::save' , 1);
            }
            // where
            if(!empty($this->_sql['where'])){
                $sql .= ' where 1 = 1 ';
                foreach ($this->_sql['where'] as $v){
                    $sql .= ' and ( '.$v.' ) ';
                }
                $this->_sql['where'] = null;
                unset($this->_sql['where']);
            }
        }
        // delete
        else if($this->_sql['act'] == 'delete'){
            $sql .= ' from ' . $this->_conf['table_name'];
            // where
            if(!empty($this->_sql['where'])){
                $sql .= ' where 1 = 1 ';
                foreach ($this->_sql['where'] as $v){
                    $sql .= ' and ( ' . $v . ' ) ';
                }
                $this->_sql['where'] = null;
                unset($this->_sql['where']);
            }
        }
        // insert
        else if($this->_sql['act'] == 'insert' || $this->_sql['act'] == 'replace'){
            $sql .= 'into ' . $this->_conf['table_name'] . '( ';
            $keys = $vals = array();
            // 数据
            if(!empty($this->data) && is_array($this->data)){
                foreach ($this->data as $k => $v){
                    $keys[] = $this->escape($k);
                    $vals[] = "'" . $v . "'";
                }
                $this->data = array();
            }
            // 无数据
            else{
                core::error(core::L('no-data' , $this->_conf['table']) , 'tab::save' , 1);
            }
            $sql .= implode(' , ' , $keys) . ' ) values( ';
            $sql .= implode(' , ' , $vals) . ' )';
        }
        // reset
        $this->data = array();
        $this->_sql = array();
        return $sql;
    }

    /**
     * 更新
     *
     */
    public function save(){
        $pkval = intval($this->data[$this->_conf['fields']['pk']] | $this->_sql['set'][$this->_conf['fields']['pk']][1]);
        if($pkval > 0 || !empty($this->_sql['where'])){
            // 若表主键值大于0 则执行update操作
            return $this->_validate(true) ? $this->_dbobj->update($this->parsesql('update')) : false;
        }else{
            // 执行 insert 操作
            return $this->_validate() ? $this->_dbobj->add($this->parsesql('insert')) : false;
        }
    }
    
    /**
     * 删除操作
     *
     * @return unknown
     */
    public function delete(){
        return $this->_dbobj->update($this->parsesql('delete'));
    }
    
    /**
     * 删除操作 别名
     *
     * @return unknown
     */
    public function del(){
        return $this->_dbobj->update($this->parsesql('delete'));
    }
    
    /**
     * 统计
     *
     * @return unknown
     */
    public function count(){
        if(empty($this->_sql['fields'])){
            $this->fields('count( ' . ( $this->_conf['fields']['pk'] ? $this->_conf['fields']['pk'] : '*') .' ) as nums'); 
        }
        return $this->_dbobj->count($this->parsesql('select'));
    }
    
    /**
     * replace 操作
     *
     * @return unknown
     */
    public function replace(){
        if(!$this->_validate()){
            return false;
        }
        return $this->_dbobj->update($this->parsesql('replace'));
    }

    /**
     * Left Join
     *
     * @param unknown_type $tab 表模型类名
     * @param unknown_type $key 当前表字段
     * @param unknown_type $fkey 左表字段
     * @return unknown
     */
    public function leftjoin($tab , $key , $fkey){
        if(trim($tab) != '' && trim($fkey) != ''){
            $fkey = str_replace($tab.'.' , '' , trim($fkey));
            $str =  "left join ".$this->_gettabname($tab) . ' on ' . $this->escape($key) .' = ' . $this->escape($tab.'.'.$fkey);
            $this->_sql['join'][] = $str;
        }
        return $this;
    }

    /**
     * Right Join
     *
     * @param unknown_type $tab 表模型类名
     * @param unknown_type $key 当前表字段
     * @param unknown_type $fkey 右表字段
     * @return unknown
     */
    public function rightjoin($tab,$key,$fkey){
        if(trim($tab) != '' && trim($key) != '' && trim($fkey) != ''){
            $fkey = str_replace($tab.'.' , '',trim($fkey));
            $str  =  "right join " . $this->_gettabname($tab) . ' on ' . $this->escape($key) .' = ' . $this->escape($tab . '.' . $fkey);
            $this->_sql['join'][] = $str;
        }
        return $this;
    }

    /**
     * Order By
     *
     * @param unknown_type $str
     * @return unknown
     */
    public function order($str){
        $str = trim($str);
        if($str != ''){
            preg_match_all('/(.+?)(desc|asc)\s*?\,?/si' , $str , $m);
            for($i = 0 , $max = count($m[1]) ; $i < $max ; $i++){
                $field = array();
                $tmp   = explode(',' , $m[1][$i]);
                foreach ($tmp as $v){
                    if(trim($v) != ''){
                        $field[] = $this->escape($v);
                    }
                }
                $this->_sql['order'][] = implode(' , ' , $field) . ' ' . $m[2][$i];
            }
        }
        return $this;
    }

    /**
     * union order
     *
     * @param unknown_type $str
     * @return unknown
     */
    public function uorder($str){
        $str = trim($str);
        if($str != ''){
            preg_match_all('/(.+?)(desc|asc)\,?/si' , $str , $m);
            for($i=0 , $max = count($m[1]) ; $i < $max ; $i++){
                $field = array();
                $tmp = explode(',' , $m[1][$i]);
                foreach ($tmp as $v){
                    $field[] = $v;
                }
                $this->_sql['uorder'][] = implode(' , ' , $field) . ' ' . $m[2][$i];
            }
        }
        return $this;

    }

    /**
     * Group By
     *
     * @param unknown_type $str
     * @return unknown
     */
    public function group($str){
        $str = trim($str);
        if($str != ''){
            $tmp = explode(',' , $str);
            foreach ($tmp as $v){
                $this->_sql['group'][] = $this->escape($v);
            }
        }
        return $this;
    }

    /**
     * 设置查询字段
     *
     * @param unknown_type $str
     * @return unknown
     */
    public function fields($str){
        $tmp = explode(',' , trim(strtolower($str)));
        foreach ($tmp as $v){
            if(strpos($v,' as ') !== false){
                $temp = explode(' as ' , $v);
                $matchs = array();
                // count() , sum() , max() , min() ... 
                preg_match('/(\w+)\(\s*?([^\)]+?)\s*?\)/i' , trim($temp[0]) , $matchs);
                if($matchs[1] && $matchs[2]){
                    $matchs = array_map('trim' , $matchs);
                    // distinct
                    if(($pos = strpos($matchs[2] , 'distinct')) === false){
                        $this->_sql['fields'][] = $matchs[1] . '( ' . $this->escape($matchs[2]) . ' ) as '.$this->escape($temp[1]);
                    }else{
                        $keyword = substr($matchs[2] , 0 , $pos + 8);
                        $field = substr($matchs[2] , $pos + 8 - strlen($matchs[2]));
                        $this->_sql['fields'][] = $matchs[1] . '( ' . $keyword . ' ' . $this->escape($field) . ' ) as '.$this->escape($temp[1]);
                    }
                }else{
                    $this->_sql['fields'][] = $this->escape($temp[0]).' as '.$this->escape($temp[1]);
                }
            }else{
                $this->_sql['fields'][] = $this->escape($v);
            }
        }
        return $this;
    }

    /**
     * 字段转义
     *
     * @param unknown_type $str
     * @return unknown
     */
    public  function escape($str){
        $ret = $str = strtolower(trim($str));
        if(isset($this->_conf['fields']['list'][$str])){
            $ret = $this->_conf['table_name'] . '.`' . $str . '`';
        }else{
            if(strpos($str,'.') !== false){
                $tmp = explode('.' , $str);
                // info_tab.id as iid
                if(strpos($str,' as ') !== false){
                    $temp = explode(' as ' , str_replace($tmp[0] . '.' , '' , $str));
                    $ret = $this->_gettabname($tmp[0]) . '.`' . trim($temp[0]) . '` as `' . trim($temp[1]) . '`';
                }else{
                    $ret = $this->_gettabname($tmp[0]).'.`' . trim($tmp[1]) . '`';
                }
            }else{
                $ret = '`' . $ret . '`';
            }
        }
        return $ret;
    }

    /**
     * 
     * 加载表单数据至当前对象data属性
     *
     * @param unknown_type $var
     * @return unknown
     */
    public function load($var = null){
        $this->_errormsg = array();
        $data = core::getgpc(empty($var) ? $this->_conf['table'] : $var );
        foreach ($data as $k => $v){
            // 过滤非当前表字段的内容
            if(in_array($k , $this->_conf['fields']['list'])){
                $this->data[$k] = $v;
            }
        }
        $data = null;
        return $this->_validate($this->data[$this->_conf['fields']['pk']]);
    }

    /**
     * 数据验证
     *
     * @return unknown
     */
    private function _validate($isupdate = false){
        // 默认返回
        $ret = true;

        // 当操作为插入的时候 , 合并默认数据 
        if(!empty($this->default) && !$isupdate){
            $this->data = array_merge($this->default , $this->data);
        }

        // 执行过滤
        foreach ($this->data as $k => &$v){
            if(isset($this->filter[$k])){
                $v = call_user_func_array($this->filter[$k] , array($this->data[$k]));
            }else{
                $v = filter::normal($v);
            }
        }
        unset($v);
        // 数据验证
        if(!empty($this->validate)){
            foreach ($this->validate as $k => $v){
                // 执行更新时候,若数据不存在,跳过; 只验证存在的数据
                if(!isset($this->data[$v['key']]) && $isupdate){
                    continue;
                }
                // 数据验证
                switch (intval($v['type'])){
                    case 0:
                        // 使用验证类提供的规则验证
                        if(!(bool)preg_match(filter::$rules[$v['rule']] , $this->data[$v['key']] )){
                            $ret = false;
                            $this->_errmsg[$v['key']] = $v['msg'];
                        }
                        break;
                    case 1:
                        // 使用自定义的正则表达式验证
                        try {
                            $result = preg_match($v['rule'] , $this->data[$v['key']]);
                        }catch(Exception $e){
                            core::error(core::L('regex-error' , $v['rule']));
                        }
                        if(!$result){
                            $ret = false;
                            $this->_errmsg[$v['key']] = $v['msg'];
                        }
                        break;
                    case 2:
                        // 使用自定义方法验证
                        if($v['rule'][0] == get_class($this)){
                            if(!(bool)call_user_func_array(array($this , $v['rule'][1]) , array($this->data[$v['key']] , $this->data[$this->_conf['fields']['pk']]))){
                                $ret = false;
                                $this->_errmsg[$v['key']] = $v['msg'];
                            }
                        }else if(!(bool)call_user_func_array($v['rule'] , array($this->data[$v['key']] , $this->data[$this->_conf['fields']['pk']]))){
                            $ret = false;
                            $this->_errmsg[$v['key']] = $v['msg'];
                        }
                        break;
                }
            }
        }
        return $ret;
    }

    /**
     * 根据类名获取表名
     *
     * @param unknown_type $class
     * @return unknown
     */
    private function _gettabname($class){
        return '`' . $this->_conf['pre'].str_replace('_tab' , '' , strtolower($class)) . '`';
    }

    /**
     * 获取错误消息
     *
     * @return unknown
     */
    public function geterr(){
        $errmsg = $this->_errmsg;
        $this->_errmsg = null;
        return $errmsg;
    }
}
?>